package biback.utils

import cats.Monad
import cats.data._
import DomainLogger._

trait GeneralAlgebra[F[_]] extends DomainObject {

  type EitherF[E <: DomainError, A] = EitherT[F, E, A]
  type ErrOrF[A] = EitherF[DomainError, A]
  type ErrOrNoneF = EitherF[DomainError, Unit]

  type LogErrOrF[A] = IorT[F, NonEmptyChain[DomainError], A]

  def foundO[A](o: Option[A], e: DomainError)(implicit F: Monad[F]): ErrOrF[A] =
    EitherT.fromOption(o, e)

  def notFoundO[A](o: Option[A], e: DomainError)(implicit F: Monad[F]): ErrOrNoneF =
    EitherT.fromEither(o.map(_ => e).toLeft(()))

  def foundF[A](o: F[Option[A]], e: DomainError)(implicit F: Monad[F]): ErrOrF[A] =
    EitherT.fromOptionF(o, e)

  def notFoundF[O](o: F[Option[O]], e: DomainError)(implicit F: Monad[F]): ErrOrNoneF =
    OptionT(o).map(_ => e).toLeft(())

  def assertF(b: Boolean, e: DomainError)(implicit F: Monad[F]): ErrOrNoneF =
    EitherT.fromEither(Either.cond(b, (), e))

  implicit class OptionErrOrFOps[A](b: Option[A]) {
    def checkF[B](f: A => ErrOrF[B])(implicit F: Monad[F]): ErrOrNoneF =
      b.map(x => f(x).map(_ => ())).getOrElse(resultF(()))
  }

  def orF[A](b: Boolean, a: A, e: DomainError)(implicit F: Monad[F]): ErrOrF[A] =
    EitherT.fromEither(Either.cond(b, a, e))

  def orF[A](e: Either[DomainError, A])(implicit F: Monad[F]): ErrOrF[A] =
    EitherT.fromEither(e)

  def optionF[A](b: Boolean, a: A)(implicit F: Monad[F]): F[Option[A]] =
    F.pure(if (b) Some(a) else None)

  def errorF[A](e: DomainError)(implicit F: Monad[F]): ErrOrF[A] = EitherT.leftT(e)

  def resultF[A](a: A)(implicit F: Monad[F]): ErrOrF[A] = liftF(F.pure(a))

  def pureF[A](a: A)(implicit F: Monad[F]): F[A] = F.pure(a)

  def liftF[A](a: F[A])(implicit F: Monad[F]): ErrOrF[A] =
    EitherT.liftF(a)

  def foundF[E <: DomainError, B](r: ErrOrF[Option[B]], e: E)(implicit F: Monad[F]): ErrOrF[B] =
    r.flatMap(foundO(_, e))

  def notFoundF[E <: DomainError, B](r: ErrOrF[Option[B]], e: E)(implicit F: Monad[F]): ErrOrNoneF =
    r.flatMap(notFoundO(_, e))

  def iorF[A](b: Boolean, a: A, e: DomainError)(implicit F: Monad[F]): LogErrOrF[A] =
    IorT.fromIor(Ior.fromEither(Either.cond(b, a, e)).toIorNec)

  def iorF[A](b: Boolean, a: A, e: DomainError, logging: LoggingLine)(implicit F: Monad[F]): LogErrOrF[A] =
    IorT.fromIor(
      for {
        _ <- Ior.both(logging, ()).toIorNec
        x <- Ior.fromEither(Either.cond(b, a, e)).toIorNec
      } yield x
    )

  def iorF[A](e: ErrOr[A])(implicit F: Monad[F]): LogErrOrF[A] =
    IorT.fromIor(Ior.fromEither(e).toIorNec)

  def iorF[A](e: ErrOrF[A])(implicit F: Monad[F]): LogErrOrF[A] =
    IorT.fromEitherF(e.value).leftMap(NonEmptyChain.one)

  def logF(logging: LoggingLine)(implicit F: Monad[F]): LogErrOrF[Unit] =
    IorT.fromIor(Ior.both(logging, ()).toIorNec)

  def errF[A](e: DomainError)(implicit F: Monad[F]): LogErrOrF[A] =
    IorT.leftT(NonEmptyChain.one(e)) // 没用到A但需要有不然调用方编译失败

  implicit def covarianceHelper[E <: DomainError, A](x: EitherT[F, E, A])
                                                    (implicit F: Monad[F]): ErrOrF[A] =
    x.leftMap(_.asInstanceOf[DomainError])

  implicit class OpsErrOr[A](it: Either[DomainError, A]) {
    def debug(msg: => String)(implicit line: sourcecode.Line, file: sourcecode.File): LogErrOr[A] =
      for {
        _ <- Ior.both(LoggingLine(file.value, line.value, Level.DEBUG, msg), ()).toIorNec
        x <- Ior.fromEither(it).toIorNec
      } yield x
  }

}
